package org.zero.onlinesubject.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.DefaultTransactionDefinition;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import org.zero.onlinesubject.common.constant.RedisConstant;
import org.zero.onlinesubject.common.param.Chunk;
import org.zero.onlinesubject.common.vo.FileVo;
import org.zero.onlinesubject.common.vo.Result;
import org.zero.onlinesubject.entity.LiveRoom;
import org.zero.onlinesubject.entity.SystemFile;
import org.zero.onlinesubject.mapper.LiveRoomMapper;
import org.zero.onlinesubject.service.SystemFileService;
import org.zero.onlinesubject.mapper.SystemFileMapper;
import org.springframework.stereotype.Service;
import org.zero.onlinesubject.utils.UserContextHolder;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 *
 */
@Service
@Slf4j
public class SystemFileServiceImpl extends ServiceImpl<SystemFileMapper, SystemFile>
    implements SystemFileService{

    @Value("${file.store_path}")
    private String storePath;

    @Value("${file.external_path}")
    private String externPath;

    @Resource
    private PlatformTransactionManager transactionManager;

    @Resource
    private Executor taskExecutor;

    @Resource
    private SystemFileMapper systemFileMapper;

    @Resource
    private LiveRoomMapper liveRoomMapper;

    @Resource
    private ThreadService threadService;

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    // 映射外部路径地址
    private Map<String,String> map = new HashMap<>();

    // 物理存放目录
    private Map<String,String> dirMap = new HashMap<>();

    @PostConstruct
    public void init(){
        map.put("pdf","/pdf");
        map.put("jpg","/pic");
        map.put("JPG","/pic");
        map.put("jpeg","/pic");
        map.put("JPEG","/pic");
        map.put("png","/pic");
        map.put("PNG","/pic");
        map.put("excel","/excel");
        map.put("webm","/cls");
        map.put("mp4","/cls");

        dirMap.put("pdf","/pdf");
        dirMap.put("excel","/excel");
        dirMap.put("jpg","/images");
        dirMap.put("JPG","/images");
        dirMap.put("JPEG","/images");
        dirMap.put("PNG","/images");
        dirMap.put("png","/images");
        dirMap.put("jpeg","/image");
        dirMap.put("webm","/webm");
        dirMap.put("mp4","/video");
    }

    // 小文件上传
    @Override
    public List<FileVo> uploadFile(MultipartFile[] files, String bizId) throws Exception {
        System.out.println("bizID: "+bizId);
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));

        CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
            for (MultipartFile file : files) {
                String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
                // 获取物理存放路径
                String dirPath = storePath + dirMap.get(ext) + "/" + format;
                if (!Files.exists(Paths.get(dirPath))) {
                    try {
                        Files.createDirectories(Paths.get(dirPath));
                    } catch (IOException e) {
                        e.printStackTrace();
                        log.error("创建文件夹失败： ",e);
                    }
                }

                String filePath = dirPath + "/" + file.getOriginalFilename();
                System.out.println("保存文件: " + filePath);
                try {
                    file.transferTo(Paths.get(filePath));
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("保存文件失败: ",e);
                }
            }
        },taskExecutor);
        List<SystemFile> list = new ArrayList<>();
        CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
            for (MultipartFile file : files) {
                SystemFile systemFile = new SystemFile();
                String filename = file.getOriginalFilename();
                long size = file.getSize();
                systemFile.setFilename(filename);
                String ext = StringUtils.getFilenameExtension(file.getOriginalFilename());
                String subPath = map.get(ext);
                String subPath2 = dirMap.get(ext);
                String accessPath = subPath + subPath2 + "/" + format + "/" + file.getOriginalFilename();
                systemFile.setFilepath(accessPath);
                systemFile.setFilesize(size);
                systemFile.setBizid(bizId);
                systemFile.setFiletype("0001");
                systemFile.setUploadtime(LocalDateTime.now());
                systemFile.setIsfinished(true);
                list.add(systemFile);
            }
            boolean flag = saveBatch(list);
            if(!flag) log.error("文件保存到数据库失败");
        },taskExecutor);
        CompletableFuture.allOf(future1,future2).get(20, TimeUnit.SECONDS);
        List<FileVo> voList = list.stream().map(item -> {
            return new FileVo(item.getFileid(),item.getBizid(),item.getFilename(),item.getFilepath());
        }).collect(Collectors.toList());
        return voList;
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public Boolean bigFileUpload(Chunk chunk) throws IOException {
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String filename = chunk.getFile().getOriginalFilename();
        String ext = StringUtils.getFilenameExtension(filename);
        String dirPath = storePath + dirMap.get(ext) + "/" + format;
        String dirPath2 = storePath + "/video/" + format ;
        String destFile = dirPath + "/" + chunk.getMd5() + "." + StringUtils.getFilenameExtension(filename);

        log.info("第"+chunk.getChunkNum()+"块数据,数据大小: "+chunk.getChunkSize());

        try {
            if (Files.notExists(Paths.get(dirPath))) {
                Files.createDirectory(Path.of(dirPath));
            }
            if (Files.notExists(Paths.get(dirPath2))) {
                Files.createDirectory(Path.of(dirPath2));
            }
        } catch (IOException e) {
            e.printStackTrace();
            log.error("创建文件夹失败： "+e.getMessage());
        }


        RandomAccessFile raf = null;
        InputStream is = null;
        try {
            raf = new RandomAccessFile(new File(destFile),"rw");
            is = chunk.getFile().getInputStream();
            raf.seek(chunk.getChunkNum() * chunk.getChunkSize());
            byte[] bytes = is.readAllBytes();
            log.info("bytes =>"+bytes.length);
            raf.write(bytes);
        } catch(IOException e){
            log.error("分片片数： "+chunk.getChunkNum()+"分片写入文件失败: "+e.getMessage());
            return false;
        } finally {
            try {
                if(is!=null){
                    is.close();
                }
                if(raf!=null){
                    raf.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                log.error("分块上传失败: ",e);
            }
        }
        return true;
    }

    @Override
    public Boolean deleteFileByBizID(List<String> bizIds) throws ExecutionException, InterruptedException, TimeoutException {
        QueryWrapper<SystemFile> wrapper = new QueryWrapper<>();
        wrapper.in(!bizIds.isEmpty(),"BizID",bizIds).eq(!bizIds.isEmpty(),"FileType","0002");
        List<SystemFile> systemFiles = systemFileMapper.selectList(wrapper);
        if(systemFiles.isEmpty()) return true;
        return getaBoolean(bizIds, systemFiles);
    }

    @Override
    public Boolean deleteFileByFileID(List<String> fileIds) throws ExecutionException, InterruptedException, TimeoutException {
        List<SystemFile> systemFiles = systemFileMapper.selectByFileIDs(fileIds);
        return getaBoolean(fileIds, systemFiles);
    }

    private Boolean getaBoolean(List<String> fileIds, List<SystemFile> systemFiles) throws InterruptedException, ExecutionException, TimeoutException {
        CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> {
            for (SystemFile systemFile : systemFiles) {
                String filePath = storePath + "/" + systemFile.getFilepath();
                try {
                    Files.deleteIfExists(Path.of(filePath));
                } catch (IOException e) {
                    e.printStackTrace();
                    log.error("删除 {} 文件失败,{}",systemFile.getFilename(),e.getMessage());
                }
            }
        }, taskExecutor);
        TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        CompletableFuture<Boolean> f2 = CompletableFuture.supplyAsync(() -> {
            try {
                return removeBatchByIds(fileIds);
            } catch (Exception e) {
                e.printStackTrace();
                log.error("数据库操作失败: {}", e.getMessage());
                transactionStatus.setRollbackOnly(); // 手动标记事务回滚
                return false;
            }
        }, taskExecutor);
        f1.get();

        if(transactionStatus.isRollbackOnly()){
            transactionManager.rollback(transactionStatus);
            return false;
        }else{
            transactionManager.commit(transactionStatus);
            return f2.get(3,TimeUnit.SECONDS);
        }
    }

    @Override
    public SystemFile getVideoById(String fileId) {
        SystemFile systemFile = systemFileMapper.selectById(fileId);
        if(systemFile==null) return null;
        String filepath = systemFile.getFilepath();
        String fullPath = externPath + filepath.substring(0,filepath.lastIndexOf(".")).concat(".m3u8");
        systemFile.setFilepath(fullPath);
        return systemFile;
    }

    @Override
    public List<SystemFile> getFileByBizId(String bizId,String fileType) {
        QueryWrapper<SystemFile> wrapper = new QueryWrapper<>();
        wrapper.eq("BizID",bizId).eq(fileType!=null&&fileType.length()>0,"FileType",fileType);
        List<SystemFile> list = systemFileMapper.selectList(wrapper);
        list.forEach(systemFile -> {
            systemFile.setFilepath(externPath + systemFile.getFilepath());
        });
        return list;
    }

    @Override
    public Boolean uploadFinish(String bizId,String filename,String md5,Long totalSize,Boolean fromLive) {
        SystemFile systemFile = new SystemFile();
        systemFile.setFilesize(totalSize);
        systemFile.setUploadtime(LocalDateTime.now());
        systemFile.setBizid(bizId);
        systemFile.setFilename(filename);
        systemFile.setFiletype("0002");

        String ext = StringUtils.getFilenameExtension(filename);
        String format = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd"));
        String subPath = map.get(ext);
        String accessPath = subPath + "/" + format + "/" + md5+".mp4";
        systemFile.setFilepath(accessPath);

        if(!"mp4".equals(ext)){
            String sourcePath = storePath + dirMap.get(ext) + "/" + format;
            String destPath = storePath + "/video/" + format;
            String sourceFile = sourcePath + "/" + md5 + "." + StringUtils.getFilenameExtension(filename);
//        System.out.println("\033[1:32m sourceFile=>"+sourceFile);
            String destFile = destPath + "/" + md5 + ".mp4";
            String userid = UserContextHolder.get().getUserid();
            stringRedisTemplate.delete(RedisConstant.LIVE_ROOM_USER+userid);
            threadService.webm2MP4(sourceFile,destFile,bizId,systemFileMapper);
        }


        if(fromLive){
            UpdateWrapper<LiveRoom> wrapper = new UpdateWrapper<LiveRoom>();
            wrapper.set("IsEnd",true).eq("LiveID",bizId);
            liveRoomMapper.update(wrapper);
        }
        return save(systemFile);
    }
}
